package com.brycen.nio.channel;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

public class ChannelTest {

    /**
     * 通道（Channel）表示IO源与目标打开的链接，类似于传统的"流"。
     * 只不过Channel不能直接访问数据，Channel只能与Buffer交互
     * 通道和流的区别：
     *      1、通道可以同时进行读写，而流只能是单向的，要么是读或者写
     *      2、通道可以实现异步读写数据
     *      3、通道可以从缓冲区读取数据，也可以写数据到缓冲区
     * 常见的Channel实现类：
     *      1、FileChannel：用于读取、写入、映射和操作文件，只能工作于阻塞模式下
     *      2、DatagramChannel：通过UDP读写网络中的数据通道
     *      3、SocketChannel：通过TCP读取网络中的数据
     *      4、ServerSocketChannel：监听新进来的TCP连接，每进来一个都会创建一个SocketChannel
     *
     * 对于网络channel编程注意事项
     *      1、一定要循环写出，因为网络编程在写出的时候是有限制的，对于大数据而言不一定能一次全部写出去
     *       while (buffer.hasRemaining()){
     *             channel.write(buffer);
     *       }
     */


    /**
     * 文件写入
     */
    @Test
    public void testFileChannel1() throws IOException {
        //1、字节输出通向目标文件
        FileOutputStream fos = new FileOutputStream("data01.txt");
        //2、获取文件输出字节流的通道
        FileChannel channel = fos.getChannel();
        //3、创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("文件通过测试，FileChannel".getBytes());
        //一定要归位，将当前position位置设置为limit位置，否则当前position是在数据之后，写出去的都是null
        byteBuffer.flip();
        //4、通过channel将缓冲区内容写出
        channel.write(byteBuffer);
        //5、关闭通道，将数据写到磁盘中
        channel.close();
    }

    /**
     * 文件读取
     */
    @Test
    public void testFileChannel2() throws IOException {
        //1、字节输入流通向目标文件
        FileInputStream fis = new FileInputStream("data01.txt");
        //2、获取文件输入字节流的通道
        FileChannel channel = fis.getChannel();
        //3、创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(30);
        int len;
        //4、通过channel读取缓冲区的内容，read的返回值是读取数据的大小，可能为0，如果到达流的尾部则为-1
        while ((len = channel.read(byteBuffer))>0){
            //读取缓冲区前一定要归位缓冲区，将当前position位置设置为limit位置
            byteBuffer.flip();
            System.out.println(new String(byteBuffer.array(),0,byteBuffer.remaining()));
            //循环还有没读完的内容，所以需要将缓冲区清空
            byteBuffer.clear();
        }
        //5、关闭通道
        channel.close();
    }

    /**
     * 文件拷贝
     */
    @Test
    public void testFileChannel3() throws IOException {
        //1、字节输入流通向目标文件
        FileInputStream fis = new FileInputStream("data01.txt");
        FileOutputStream fos = new FileOutputStream("data01_copy.txt");
        //2、获取文件输入字节流的通道
        FileChannel channelIn = fis.getChannel();
        FileChannel channelOut = fos.getChannel();
        //3、创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(30);
        int len;
        //4、通过channel读取缓冲区的内容，read的返回值是读取数据的大小，可能为0，如果到达流的尾部则为-1
        while ((len = channelIn.read(byteBuffer))>0){
            System.out.println(len);
            //读取缓冲区前一定要归位缓冲区，将当前position位置设置为limit位置
            byteBuffer.flip();
            channelOut.write(byteBuffer);
            //循环还有没读完的内容，所以需要将缓冲区清空
            byteBuffer.clear();
        }
        System.out.println(len);
        //5、关闭通道，将数据写到磁盘中
        channelIn.close();
        channelOut.close();
    }

    /**
     * 分散读取和聚集写入
     */
    @Test
    public void testFileChannel4() throws IOException {
        //1、字节输入流通向目标文件
        FileInputStream fis = new FileInputStream("data01.txt");
        FileOutputStream fos = new FileOutputStream("data01_copy.txt");
        //2、获取文件输入字节流的通道
        FileChannel channelIn = fis.getChannel();
        FileChannel channelOut = fos.getChannel();
        //3、创建缓冲区
        ByteBuffer byteBuffer1 = ByteBuffer.allocate(30);
        ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
        //组合缓冲区
        ByteBuffer[] buffers = {byteBuffer1,byteBuffer2};

        //4、分散读取，将内容读取到很多缓冲区中，此时的read返回的是所有缓冲区的数据大小，可能为0，如果读到流的末尾会是-1
        channelIn.read(buffers);
        for (ByteBuffer buffer : buffers) {
            buffer.flip();//归位，设置当前位置为limit，开启读模式
        }

        //5、聚集写入
        channelOut.write(buffers);

        //5、关闭通道
        channelIn.close();
        channelOut.close();
    }

    /**
     * transferFrom：从目标通道中去复制目标通道数据
     * transferTo：将源通道内容转移到目标通道
     * 优点：效率高，底层会利用操作系统的零拷贝进行优化
     * 缺点：最大只能传输2g数据，但是可以优化，就是循环写出
     *      for (long left = channelIn.size();left>0;){
     *          //因为在transfer的时候channel的position是不会变的，所以我们循环transfer的时候就不能用position()作为起始位置
     *          //channelIn.size()-left：从当前位置开始传输
     *          //left：传输多少数据量
     *          long transferSize = channelIn.transferTo(channelIn.size()-left,left,channelOut);
     *          //减去已经传输的数据量
     *          left -= transferSize;
     *      }
     */
    @Test
    public void testFileChannel5() throws IOException {
        //1、字节输入流通向目标文件
        FileInputStream fis = new FileInputStream("data01.txt");
        FileOutputStream fos = new FileOutputStream("data01_copy.txt");
        //2、获取文件输入字节流的通道
        FileChannel channelIn = fis.getChannel();
        FileChannel channelOut = fos.getChannel();

//        channelOut.transferFrom(channelIn,channelIn.position(),channelIn.size());
//        channelIn.transferTo(channelIn.position(),channelIn.size(),channelOut);

        //循环复制，因为transferTo最大只能传输2g数据，所以需要循环transferTo
        for (long left = channelIn.size();left>0;){
            //因为在transfer的时候channel的position是不会变的，所以我们循环transfer的时候就不能用position()作为起始位置
            //channelIn.size()-left：从当前位置开始传输
            //left：传输多少数据量
            //transferSize：此次实际传输的数据量
            long transferSize = channelIn.transferTo(channelIn.size()-left,left,channelOut);
            //减去已经传输的数据量
            left -= transferSize;
        }
        
        //5、关闭通道
        channelIn.close();
        channelOut.close();
    }
}
